\section{杂项记录}

\subsection{C语言高级应用}

\subsubsection{C语言的宏}

\paragraph{常用功能}
\begin{enum}
    \item \texttt{\#} 字符串化
    \item \texttt{\#\#} 字符串拼接
    \item \verb|#pragram message("string")|
\end{enum}

\paragraph{宏展开顺序规则}

\begin{enum}
    \item 将实参代入宏的替换体中，无论实参是否包含宏
    \item 若实参前后紧邻 \texttt{\#} 和 \texttt{\#\#} 则无论实参中是否含宏都不再展开\\
          否则对实参重复进行宏展开，直至无法再宏展开
    \item 最后再次处理替换体，展开剩余的宏
\end{enum}

\begin{lstlisting}[language=C]
#define cat1(x,y) x ## y 
#define cat2(x,y) cat1(x,y)
/* ----------  方案一  ---------- */
cat1(cat1(1,2), 3)
cat1(1,2) ## 3       // 实参紧邻 ## 不再继续展开
/* ----------  方案二  ---------- */
cat2(cat2(1,2), 3)
cat1(cat2(1,2), 3)   // 先处理实参，实参不紧邻 # 或 ##
cat1(cat1(1,2), 3)
cat1(1 ## 2, 3)      // TODO: 这里先处理1##2还是cat1？
cat1(12, 3)          // 实参处理完毕，再次处理替换体
123
\end{lstlisting}

特殊规则：在一次递归宏展开的过程中，若出现了曾展开过的部分，则展开停止
\begin{lstlisting}[language=C]
#define x (4 + y)
#define y (2 * x)
x   =>   (4 + y)   =>   (4 + (2 * x))  // x曾被展开过，此后不再展开
y   =>   (2 * x)   =>   (2 * (4 + y))  // y曾被展开过，此后不再展开
\end{lstlisting}

\subsection{Struct与Union}

\subsubsection{结构体变量的储存方式}
结构体成员在内存中的排布规则：
\begin{enum}
    \item 结构体变量的首地址可被其最宽基本类型成员的大小整除
    \item 结构体各成员相对结构体首地址的偏移量是自身大小的整数倍
    \item 结构体的总大小是其最宽基本类型成员大小的整该成员数倍
    \item 若有需要，编译器会在成员间添加空字节
    \item 先定义的成员在低字节，后定义的成员在高字节
\end{enum}

\begin{center}
\begin{minipage}{0.45\textwidth}
\begin{ccode}
typedef struct{
    uint8_t  m1;
    uint64_t m2;
    uint32_t m3;
    uint16_t m4;
} ST1;
\end{ccode}
\end{minipage}
\qquad
\begin{minipage}{0.45\textwidth}
\begin{ccode}
typedef struct{
    uint8_t  m1;
    uint16_t m2;
    uint32_t m3;
    uint64_t m4;
} ST2;
\end{ccode}
\end{minipage}
\end{center}

编译后可得：\lstinline[language=C]{sizeof(ST1)} 为24，\lstinline[language=C]{sizeof(ST2)} 为16，
两者内存排布如图 \ref{fig:结构体成员在内存中的排布规律} 所示

\begin{figure}[htpb]
    \centering
    \begin{tikzpicture}
        \renewcommand{\myvarx}{0.6}
        \renewcommand{\myvary}{-0.6}
        \draw[fill=blue!10] (0*\myvarx, 0*\myvary) rectangle node{\texttt{m1}} ++(1*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (0*\myvarx, 1*\myvary) rectangle node{\texttt{m2}} ++(8*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (0*\myvarx, 2*\myvary) rectangle node{\texttt{m3}} ++(4*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (4*\myvarx, 2*\myvary) rectangle node{\texttt{m4}} ++(2*\myvarx, 1*\myvary);
        \draw (0,0*\myvary) node[below left]{\color{gray}\sffamily 00} rectangle (8*\myvarx, 1*\myvary);
        \draw (0,1*\myvary) node[below left]{\color{gray}\sffamily 08} rectangle (8*\myvarx, 2*\myvary);
        \draw (0,2*\myvary) node[below left]{\color{gray}\sffamily 16} rectangle (8*\myvarx, 3*\myvary);
    \end{tikzpicture}
    \qquad\qquad
    \begin{tikzpicture}
        \renewcommand{\myvarx}{0.6}
        \renewcommand{\myvary}{-0.6}
        \draw[fill=blue!10] (0*\myvarx, 0*\myvary) rectangle node{\texttt{m1}} ++(1*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (2*\myvarx, 0*\myvary) rectangle node{\texttt{m2}} ++(2*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (4*\myvarx, 0*\myvary) rectangle node{\texttt{m3}} ++(4*\myvarx, 1*\myvary);
        \draw[fill=blue!10] (0*\myvarx, 1*\myvary) rectangle node{\texttt{m4}} ++(8*\myvarx, 1*\myvary);
        \draw[white] (0,2*\myvary) node[below left]{\color{gray}\sffamily 16} rectangle (8*\myvarx, 3*\myvary);
        \draw[black] (0,0*\myvary) node[below left]{\color{gray}\sffamily 00} rectangle (8*\myvarx, 1*\myvary);
        \draw[black] (0,1*\myvary) node[below left]{\color{gray}\sffamily 08} rectangle (8*\myvarx, 2*\myvary);
    \end{tikzpicture}
    \caption{\texttt{ST1} 与 \texttt{ST2} 各成员在内存中的排布}
    \label{fig:结构体成员在内存中的排布规律}
\end{figure}

\subsubsection{匿名Struct与匿名Union}
嵌套在其它struct/union中的struct/union可以是匿名的，其成员直接归属于上级struct/union

\begin{ccode}
typedef struct{
    char str[8];
    union{                                               // 第一层嵌套 (union)
        uint32_t data;
        struct{                                          // 第二层嵌套 (struct)
            uint8_t data_0, data_1, data_2, data_3;      // 后定义的在高位
        };
    };
} ST1;

int main(int argc, char *argv[])
{
    ST1 st = {
        .str = "hello",
        .data = 0x12345678          // 可直接访问所嵌套的匿名struct/union中的成员
    };
    printf("%2x %2x %2x %2x\n", st.data_3, st.data_2, st.data_1, st.data_0);

    st.data = 0xabbccdde;
    printf("%s\n", st.str);
    printf("%2x %2x %2x %2x\n", st.data_3, st.data_2, st.data_1, st.data_0);
    return 0;
}
\end{ccode}

运行结果为：
\begin{textcode}
12 34 56 78
hello
ab bc cd de
\end{textcode}

\subsubsection{位域bitfield}
若struct/union中的一些成员无需占用所有其类型的所有bit，可利用bitfield进行合并

\begin{ccode}
typedef union {
    uint8_t data;
    struct{
        uint8_t b0 : 1;  // LSB
        uint8_t b1 : 1;
        uint8_t b2 : 1;
        uint8_t b3 : 1;
        uint8_t b4 : 1;
        uint8_t b5 : 1;
        uint8_t b6 : 1;
        uint8_t b7 : 1;  // MSB
    };
} bits_t;

int main(int argc, char *argv[])
{
    bits_t bits;
    printf("Size of bits_t: %lu\n", sizeof(bits_t));
    for (int i = 0; i < 8; i++)
    {
        bits.data = 0x01 << i;
        printf("%1d %1d %1d %1d %1d %1d %1d %1d\n", 
                bits.b7, bits.b6, bits.b5, bits.b4, bits.b3, bits.b2, bits.b1, bits.b0);
    }
    return 0;
}
\end{ccode}

运行结果为：
\begin{textcode}
Size of bits_t: 1      // b0~b7 总共占用 1 byte
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
0 0 0 0 0 1 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 0 0 0 0 0 0
\end{textcode}

若某些bit是不需要的，可以用空位域作占位符，如下：
\begin{ccode}
typedef union{
    uint8_t data;
    union{
        uint8_t low4  : 4;
        uint8_t       : 1;    // 空位域，用作占位符
        uint8_t high3 : 3;
    };
} bit4s_t;
\end{ccode}

\subsection{可变参数stdarg}
对变参函数，传参时会发生default argument promotions使得所传参数类型改变
\begin{enum}
\item \mintinline{c}{char}、\mintinline{c}{short} 转化为 \mintinline{c}{int} 后传参
\item \mintinline{c}{unsigned char}、\mintinline{c}{unsigned short} 转化为 \mintinline{c}{unsigned int} 后传参
\item \mintinline{c}{float} 转化为 \mintinline{c}{double} 后传参
\end{enum}

不同架构的计算机对待变参的传参方式不同
\begin{enum}
\item x86 等架构完全采用栈来传参，因此读取变参时只需使指针按需移动
\item x86\_64 与 riscv 等架构先用寄存器来穿变参，若寄存器不足则再用栈传递剩余变参，读取变参由编译器负责
\end{enum}

\begin{quote}
    x86 架构的传参方式（调用约定）具有多种：\\
    \_\_stdcall是Windows API默认调用约定，全部参数由栈传递\\
    \_\_cdecl是C/C++的默认调用约定，全部参数由栈传递\\
    \_\_fastcall调用约定，前两个双字用寄存器传递，剩余参数用栈传递\\
    但对于可变参数的成员函数，始终使用\_\_cdecl的转换方式
\end{quote}

C语言中的变参函数示例：
\begin{ccode}
#include <stdarg.h>
#include <stdio.h>

void arg(int n, ...){
    va_list ap;
    va_start(ap, n);

    printf("%.2f  ", (float)va_arg(ap, double)); // 传入float要用double来接收
    for(int i=0; i<n; i++){
        printf("%c", (char)va_arg(ap, int));     // 传入char要用int来接收
    }
    printf("%s\n", va_arg(ap, char*));           // 传入指针类型可正常接收

    va_end(ap);
}

int main(int argc, char *argv[]){
    arg(5, 3.14f, 'h', 'e', 'l', 'l', 'o', ", world");
    return 0;
}
\end{ccode}

运行结果为：
\begin{textcode}
3.14  hello, world
\end{textcode}


\subsection{编译选项}

\subsubsection{依赖文件处理}
\begin{enum}
    \item \makebox[0.9cm][l]{\texttt{-M}  }：以Makefile风格输出编译所依赖的其它文件（.c, .h等），且仅进行预处理
    \item \makebox[0.9cm][l]{\texttt{-MM} }：同 \texttt{-M} 但不输出标准Include路径下已有的文件
    \item \makebox[0.9cm][l]{\texttt{-MD} }：同 \texttt{-M} 但默认将结果保存为\ .d 文件，且不止步于预处理
    \item \makebox[0.9cm][l]{\texttt{-MMD}}：同 \texttt{-MD} 但不输出标准Include路径下已有的文件
\end{enum}

\subsubsection{Sanitizer}
C/C++ 的运行错误检测工具
\begin{enum}
    \item \makebox[3.3cm][l]{Address Sanitizer  }：错误的内存访问（free后访问，越界）
    \item \makebox[3.3cm][l]{Leak Sanitizer     }：内存泄漏检测
    \item \makebox[3.3cm][l]{Undefined Sanitizer}：Undefined Behaviour检测
    \item \makebox[3.3cm][l]{Thread Sanitizer   }：进程间数据竞争
\end{enum}

此工具已集成在gcc中，详见 \texttt{-fsanitizer} 相关选项


\section{最简单的计算机}

\subsection{源码阅读}

\subsubsection{工具宏}

文件 \textsf{include/macro.h}

\paragraph{简易条件编译}
\begin{lstlisting}[language=C]
// macro testing
#define CHOOSE2nd(a, b, ...) b
#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b)
#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b)
// define placeholders for some property
#define __P_DEF_0  X,
#define __P_DEF_1  X,
#define __P_ONE_1  X,
#define __P_ZERO_0 X,
// define some selection functions based on the properties of BOOLEAN macro
#define MUXDEF(macro, X, Y)  MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)
#define MUXNDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, Y, X)
#define MUXONE(macro, X, Y)  MUX_MACRO_PROPERTY(__P_ONE_, macro, X, Y)
#define MUXZERO(macro, X, Y) MUX_MACRO_PROPERTY(__P_ZERO_,macro, X, Y)
\end{lstlisting}

分析调用 \verb|MUXDEF(macro, val1, val2)| 时的宏展开过程：
\begin{lstlisting}[language=C]
MUXDEF(macro, val1, val2)
MUX_MACRO_PROPERTY(__P_DEF_, raw, val1, val2)       // 此时macro已被完全展开为终值raw
MUX_WITH_COMMA(concat(__P_DEF_, raw), val1, val2)   // concat为字符串连接宏，此后先展开参数
MUX_WITH_COMMA(__P_DEF_raw, val1, val2)             // 之后应讨论raw为1，0还是undefined

/* ---------- 若raw实际为1或0 ---------- */
MUX_WITH_COMMA(__P_DEF_1, val1, val2)
MUX_WITH_COMMA(X, , val1, val2)                     
// TODO: 这里理论上应当发生错误，但实际上似乎 X, 被当作整体传递了
//       又或者是宏展开顺序有所差异
CHOOSE2nd(X, val1, val2)
val1

/* ---------- 若raw是未定义的 ---------- */
MUX_WITH_COMMA(__P_DEF_, val1, val2)
CHOOSE2nd(__P_DEF_ val1, val2)
val2
    
\end{lstlisting}

\section{冯诺依曼计算机系统}

\subsection{RISCV指令集}
相关手册：\href{http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf}{中文手册}、
\href{https://riscv.org/technical/specifications/}{英文最新手册}

\subsubsection{RV32I基础整数指令}
基本指令格式仅有6种，如图 \ref{fig:RV32I的6种基本指令格式} 所示
\begin{enum}
    \item 用于寄存器-寄存器操作的 R 类型指令
    \item 用于短立即数和访存 load 操作的 I 型指令
    \item 用于访存 store 操作的 S 型指令 
    \item 用于条件跳转操作的 B 类型指令
    \item 用于长立即数的 U 型指令
    \item 用于无条件跳转的 J 型指令
\end{enum}

\begin{figure}[htpb]
    \centering
    \includegraphics[width=0.8\textwidth]{figures/RV32I的6种基本指令格式.png}
    \caption{RV32I的6种基本指令格式}
    \label{fig:RV32I的6种基本指令格式}
\end{figure}

设计优势：
\begin{enum}
    \item 指令仅有6种，且为32位定长，简化指令解码
    \item 指令提供3个寄存器操作数，避免了额外的move指令
    \item 所有指令格式中寄存器标识符位置固定，在解码完成前就可开始访问寄存器
    \item 立即数的最高位总在指令最高位，在解码完成前就可开始立即数的符号扩展
    \item B型指令与S型指令、U型指令与J型指令之间仅由立即数排布不同，本质上只有4种指令类型
    \item 全0、全1指令均为非法，会触发异常，可帮助调试
    \item 立即数总是进行符号扩展，因此可表示0附近的负数（因此没有立即数版本的sub）
\end{enum}

\begin{quote}
    使用异或来交换两个值：
    \begin{center}
        \begin{tabular}{lll}
            \toprule
            \multicolumn{1}{c}{操作} & \multicolumn{2}{c}{注释} \\
            \midrule
            \texttt{xor x1, x1, x2} & $x_{11} = x_1 \oplus x_2$              & $x_{21} = x_2$ \\
            \texttt{xor x2, x1, x2} & $x_{12} = x_{11} = x_1 \oplus x_2$     & $x_{22} = x_1\oplus x_2 \oplus x_2= x_1$ \\
            \texttt{xor x1, x1, x2} & $x_{13} = x_1\oplus x_2\oplus x_1=x_2$ & $x_{23} = x_1$ \\
            \bottomrule
        \end{tabular}
    \end{center}
\end{quote}

\subsection{源码阅读}

\subsubsection{指令执行}
NEMU将每个指令映射为一个函数

